#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/mman.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include "streams.h"


void print_buf(int *buf,int len)
{
//	printf("%d:%s\n",__LINE__,__FILE__);
    int i;
    for(i = 0 ; i < len ; i++)
        printf("%d\n",buf[i]);
	
}

/*  create a wstream
*/
int wstream_create(wstream **wstream_ptr_addr , const char *path, int type, int size)
{
	wstream *ptr;

	*wstream_ptr_addr = (wstream *) malloc(sizeof(wstream));
	ptr = *wstream_ptr_addr; 
	
	if(ptr == NULL)
	{
		printf("Couldn't allocate memory for stream\n");
		return -1;
	}

	if(strlen(path) == 0)
	{
		printf("Please enter a valid filename\n");
		free(ptr);
		return -1;
	}
    ptr->file = (char*)malloc(strlen(path)+1);

	strcpy(ptr->file, path);
	ptr->stream_type = type;
	ptr->buf_size = size;
    ptr->offset = 0;

	if(size != 0)
	{
		if(!(ptr->buf = (int *) malloc(sizeof(int)*ptr->buf_size)))
		{
			free(ptr->file);
			free(ptr);
            perror("Malloc error\n");
		}
	}
	else
	{
		printf("Please enter a valid size\n");
		free(ptr->file);
		free(ptr);
		return -1;
	}

	switch (ptr->stream_type)
	{
		case SYSCALL:   //read()
              	ptr->fd = open(ptr->file, O_CREAT | O_RDWR, 0777);
				if(ptr->fd == -1)
				{
					printf("open failed\n");
					free(ptr->file);
					free(ptr->buf);
					free(ptr);
					return -1;
				}
				break;

		case STDIO: // fread()
				ptr->fp = fopen(ptr->file, "w+");
				if(ptr->fp == NULL)
				{
					printf("fopen failed\n");
					free(ptr->file);
					free(ptr->buf);
					free(ptr);
					return -1;
				}
				break;

		case MMAP: // mmap()
              	ptr->fd = open(ptr->file, O_CREAT | O_RDWR);
				if(ptr->fd == -1)
				{
					printf("open failed\n");
					free(ptr->file);
					free(ptr->buf);
					free(ptr);
					return -1;
				}
				break;

        default: printf("We should not be here\n");
				free(ptr->file);
				free(ptr->buf);
				free(ptr);
                 return -1;
	}

	return 0;
}


/* open a stream */
int rstream_open(rstream **rstream_ptr_addr, const char *path, int type, int size)
{
	rstream *ptr;

	*rstream_ptr_addr = (rstream *) malloc(sizeof(rstream));
	ptr = *rstream_ptr_addr; 
	
	if(ptr == NULL)
	{
		printf("Couldn't allocate memory for rstream\n");
		return -1;
	}

	if(strlen(path) == 0)
	{
		printf("Please enter a valid filename\n");
		free(ptr);
		return -1;
	}

    ptr->file = (char*)malloc(strlen(path)+1);

	strcpy(ptr->file, path);
	ptr->stream_type = type;
	ptr->buf_size = size;
    ptr->offset = ptr->buf_size;
	ptr->eof = FALSE;
	ptr->eof_index = 0;

	if(size != 0)
	{
		if(!(ptr->buf = (int *) malloc(sizeof(int)*ptr->buf_size)))
		{
			free(ptr->file);
			free(ptr);
            perror("Malloc error\n");
		}
	}
	else
	{
		printf("Please enter a valid size\n");
		free(ptr->file);
		free(ptr);
		return -1;
	}

	switch (ptr->stream_type)
	{
		case SYSCALL:      //read()
                ptr->fd = open(ptr->file, O_RDONLY);
				if(ptr->fd == -1)
				{
					free(ptr->file);
					free(ptr->buf);
					free(ptr);
					printf("rstream_open: open failed\n");
					return -1;
				}
				break;

		case STDIO: // fread()
				ptr->fp = fopen(ptr->file, "r");
				if(ptr->fp == NULL)
				{
					free(ptr->file);
					free(ptr->buf);
					free(ptr);
					printf("fopen failed\n");
					return -1;
				}
				break;

		case MMAP: // mmap()
				ptr->fd = open(ptr->file, O_RDONLY);
				if(ptr->fd == -1)
				{
					free(ptr->file);
					free(ptr->buf);
					free(ptr);
					printf("open failed\n");
					return -1;
				}
				break;

        default: printf("We should not be here\n");
				free(ptr->file);
				free(ptr->buf);
				free(ptr);
                 return -1;
	}
	return 0;
}

/* Read an element from buffer and return it */
int rstream_read_next(rstream *ptr)
{
	int status;

	switch (ptr->stream_type)
	{
		case SYSCALL: // read()
				/* all elems read from buffer or no elems in buffer */
				if(ptr->offset + ptr->eof_index >= ptr->buf_size)
				{
					/* Read elems from file and store in buffer */
					status = read(ptr->fd, ptr->buf, ptr->buf_size*sizeof(int)) / sizeof(int);

					if(status < 0)
					{
						perror("rstream_read_next: Error reading file\n");
						return -1;
					}
					else if(status == 0)
					{
						/* Return EOF if 0 bytes read */
						ptr->eof = TRUE;
					}
					else if(status < ptr->buf_size)
					{
						ptr->eof_index = ptr->buf_size - status;
					}
					/* Reset offset to 0 */
					ptr->offset = 0;	
				}

				if(end_of_rstream(ptr) == FALSE)
					return ptr->buf[ptr->offset++];
				else
					return -1;

				break ;

		case STDIO: // fread()
				/*	 Has it's own buffering scheme */	
				status = fread(ptr->buf, sizeof(int), 1, ptr->fp);

				if(status == 0)
				{
					if(feof(ptr->fp))
					{
						clearerr(ptr->fp);
						ptr->eof = TRUE;
						return -1;
					}
					else if(ferror(ptr->fp))
					{
						perror("rstream_read_next: error fread()-ing file");
					}
				}
				else
					return ptr->buf[0];

				break;

		case MMAP: // mmap()
            break;
        default:
            printf("Should not be here\n");
            return -1;
    }
}

int end_of_rstream(rstream *ptr)
{
	if(ptr->eof == TRUE) 
	{
		return TRUE;
	}
	if(	(ptr->eof_index > 0) &&
		((ptr->eof_index + ptr->offset - 1) == ptr->buf_size))
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

int wstream_write(wstream *ptr, int num)
{
	int status = 0;
    int user_offset = 0;
    int elems_written;

	switch (ptr->stream_type)
	{
		case SYSCALL: // write()
				/* */

				if(ptr->offset >= ptr->buf_size)
				{
					status = write(ptr->fd, ptr->buf, ptr->buf_size*sizeof(int));

					if(status < 0)
					{
						perror("Error writing to file");
					}

					if(status == 0)
					{
						return -1;
					}

					ptr->offset = 0;
				}

				ptr->buf[ptr->offset++] = num;
				
				return 0;

		case STDIO:
				/* */
				status = fwrite(&num, sizeof(int), 1, ptr->fp);

				if(status <= 0)
				{
					if(ferror(ptr->fp))
					{
						clearerr(ptr->fp);
						perror("wstream_write: fwrite error\n");
					}
				}

				return 0;

				break;
		
		case MMAP:
			break;
		
		default:
			printf("We should not be here\n");
			return -1;
	}
}
				

int wstream_close(wstream *ptr)
{
	switch (ptr->stream_type)
	{
		case SYSCALL:
			if(ptr->offset != 0)
			{
				if(write(ptr->fd, ptr->buf, ptr->offset*sizeof(int)) < 0)
				{
					perror("wstream_close: write failed\n");
				}
			}			

			if(close(ptr->fd) < 0)
			{
				printf("wstream_close: error closing stream\n");
				return -1;
			}

			break;

		case STDIO:

			if(fclose(ptr->fp) != 0)
			{
				printf("wstream_close: error closing stream\n");
				return -1;
			}

			break;
		
		case MMAP:
			break;
		default:
			printf("We shouldn't be here\n");
	}

	free(ptr->file);
	free(ptr->buf);
	free(ptr);

	return 0;
}

/*
int main(int argc, char *argv[])
{
	wstream *op_stream;
	rstream *input_stream;

    int num = 0;
	int op_buf_size = 6;
	int ip_buf_size = 2;

    if(wstream_create(&op_stream, "./op_data", STDIO, op_buf_size) == -1)
	{
        printf("Create error\n");
    }

    if(rstream_open(&input_stream, "./file-0", STDIO, ip_buf_size) == -1)
	{
        printf("Open error\n");
    }


	while((num = rstream_read_next(input_stream)) != -1)
	{
		// printf("%d ", num);
		if(wstream_write(op_stream, num) < 0)
		{
			printf("Couldn't write\n");
		}
	}
		
	if(end_of_rstream(input_stream) == TRUE)
	{
	//	printf("EOF reached\n");
	}

	wstream_close(op_stream);

	return 0;
}
*/
